/**
 * @file msgserver.c
 * @author Marco Luisi
 *  Created on: 15 Dec 2010
 *
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>

#include "genHash.h"
#include "genList.h" //togliere?
#include "msg.h"
#include "server_util.h"
#include "msgserv.h"
//#include "comsock.h"
#include "worker.h"
#include "writer.h"

FILE *logfile_fd;
pthread_t wr_tid;
pthread_t *workers_tid;

message_t login_msg;
message_t reply_msg;
hashTable_t* users;


user_pointers_l *online_list;

pthread_t temptid;

//vars socket
int msgsock_fd;
//struct sockaddr_un msgsock_a;

/*mutexes*/
pthread_mutex_t users_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t online_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t writer_mtx = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER; /* cond variable*/
pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t queue_mtx = PTHREAD_MUTEX_INITIALIZER;
; /* mutex*/

volatile sig_atomic_t pending_signal = 0;

/**
 *
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char **argv) {

	user_pointer_t *iter;
	user *wait;

	if (argc != 3) { //cambiare se si vuole fare opzione per background
		printf(
				"MSGSERVER: Errore, fornire entrambi i parametri di lancio del server!\n");
		exit(-1); //exit failure
	}
	login_msg.buffer = NULL;
	reply_msg.buffer = NULL;

	EC_M1_E(server_init(argv[1], argv[2]));

	serve();


	EC_M1_P(close(msgsock_fd),"close msgsock");
	//printf("dopo chiusura msgsock\n");
	perror("server closeocket finale");

	(void) unlink(MSGSOCK_PATH);//void?
	//printf("dopo unlikn\n");


	/*Attesa della terminazione workers*/
	iter = online_list->head;
	while(iter !=NULL){
		wait = iter->user;
		//close(wait->sfd);
		//printf("SERVER wait = %s socket %d TID è %d\n", wait->name, wait->sfd,wait->tid);
		//pthread_kill(wait->tid, SIGIO);
		//close(wait->sfd);
		shutdown(wait->sfd,SHUT_RDWR);
		pthread_join(wait->tid, NULL);
		//printf("MSGSERVER dopo join\n");
		iter = iter->next;
		//printf("MSGSERVER dopo iter next\n");
		if(iter != NULL){
			//printf("iter next != NULL\n");
		}
	}
	//printf("dopo join workers\n");
	/*Chiusura writer*/
	printf("MSGSERV PRIMA DI CHIUSURA WRITER\n");
	pthread_mutex_lock(&queue_mtx);
	printf("MSGSERV DOPO LOCK WRITER\n");
	pthread_cond_signal(&empty_cond);
	printf("MSGSERV DOPO COND SIG\n");
	pthread_mutex_unlock(&queue_mtx);
	printf("MSGSERV DOPO UNLOCK WRITER\n");

	pthread_join(wr_tid, NULL);
	printf("MSGSERV USCITO\n");
	exit(EXIT_SUCCESS);
}

//da ricontrollare init, fare in modo che faccia return error in modo corretto, nel caso la perror
//si fa da lmain
/**
 *
 * @param filename
 * @param logname
 * @return
 */
int server_init(char* filename, char* logname) { //cambiare nome a filename

	char* name_buffer;
	char* parse_buffer;
	FILE *userlist_fd;
	int i;
	int len;
	user *new_user = NULL;

	/*Gestione segnali*/
	EC_M1(signal_handling())

	/*Inizializzazione delle strutture dati utilizzate dal server*/
	users = new_hashTable(USRNUM, compare_user, copy_string, copy_user,
			hash_string);

	EC_NULL_E(online_list = malloc(sizeof(user_pointers_l)),ERR_MEM);
	online_list->head = NULL;
	online_list->next = NULL;

	if (strlen(filename) > NAME_MAX) {
		printf("MSGSERVER: Errore, nome file lista oltre il limite di sistema!");
		exit(EXIT_FAILURE);
	}
	if (strlen(logname) > NAME_MAX) {
		printf("MSGSERVER: Errore, nome file di log oltre il limite di sistema!");
		exit(EXIT_FAILURE);
	}

	/*Lettura lista file e riempimento tab hash*/
	EC_NULL_E(userlist_fd = fopen(filename, "r"), "MSGSERVER: Errore apertura file!");

	//NON USO UN BUFFER FORSE NON SERVE?!?!?!
	EC_NULL_E(parse_buffer = malloc(USERNAMELEN * sizeof(char)), ERR_MEM);
	EC_NULL_E(name_buffer = malloc(USERNAMELEN * sizeof(char)), ERR_MEM);

	while (fgets(parse_buffer, (NAME_MAX + 1), userlist_fd) != NULL) { // +1
		if (sscanf(parse_buffer, "%s", name_buffer) != -1) {
			//controllo alphanum?!
			len = strlen(parse_buffer) - 1; // -1 per il \n finale?!?!?
			for (i = 0; i < len; i++) {
				if (isalnum(parse_buffer[i]) == 0) {
					printf(
							"MSGSERVER: Errore nome utente nella lista non conforme: %s  i = %d name_buffer = %s\n",
							parse_buffer, i, name_buffer);
					return -1;
				}
			}
			EC_NULL_E(new_user = malloc(sizeof(user)), ERR_MEM);
			new_user->name = strdup(name_buffer); //non serve strndup i limit sono già stati controllati
			new_user->sfd = 0;
			new_user->tid = 0;
			/*Inserimento in tabella*/
			if (add_hashElement(users, name_buffer, new_user) == -1) {
				printf("addhash fallita \n");
				//che fare?!
			}

		}
		//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		free(new_user->name);
		free(new_user);
		new_user = NULL;
	}

	free(parse_buffer);
	free(name_buffer);

	fclose(userlist_fd); // fare EC anche su close? spostare?

	if (mkdir("tmp", O_CREAT | 0666) == -1 && errno != EEXIST) { //migliorabile?
		perror("Errore nella creazione della directory tmp!");
		exit(errno);
	}

	/*apertura socket*/
	(void) unlink(MSGSOCK_PATH);
	msgsock_fd = createServerChannel(MSGSOCK_PATH); //usare macro
	if (msgsock_fd == -1) {
		//messaggio errore con perror
		exit(EXIT_FAILURE);
	}

	EC_Z( pthread_create(&wr_tid,NULL,writer_start,logname) );
	return 0;

}
/**
 *
 */
void serve() {
	int bytes;
	int check;
	int aux;
	int idx;
	user* temp_user;
	int client_con;
	char buffer[256]; // cambiare?
	pthread_t ttid;

	bzero(buffer, 256);//perchè?

	/*Accettazione connessioni dai client*/
	while (pending_signal == 0) {
		//printf("ciclo ascolto!\n");
		client_con = acceptConnection(msgsock_fd);
		//printf("signal = %d!\n",pending_signal);

		//alla ricezione del segnale va avanti ugualmente
		//SISTEMARE PER FORZA!!!
		//printf("server: dopo acceptconn client con = %d! errno = %d\n", client_con, errno);
		//perror("MSGSERVER DOPO ACCEPT");

		/*Controllo di eventuali errori nella comunicazione su socket*/
		if (client_con == -1 && errno == EINTR) {//prima era diverso da eintr
			perror("MSGSERV: Errore acceptConnection: ");//si prova a proseguire
			break; //continue;
		}
		//fare una funz che incapsuli la receive ingobando controllo errore?
		//farlo.. se c'è errore non si deve proseguire..
		bytes = receiveMessage(client_con, &login_msg);
		//migliorare questo controllo?
		if (login_msg.type == MSG_CONNECT) {
			//rimettere i lock!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
			//pthread_mutex_lock(&users_mtx);//togliere poi
			//si potrebbe fare che gli worker lockano la listaonline e basta
			//qui prima si locka la lista utenti e poi quella online..
			check = user_check(login_msg.buffer);
			//printf("DOPO usercheck\n");
			switch (check) {
			case 0: {
				/*Si utilizza find_ListElemen in quanto serve il puntatore alla struct user e non una copia*/
				idx = users->hash(login_msg.buffer, users->size);// se si tiene il tid qui fd è inutile..
				temp_user = ((user*) (find_ListElement(users->table[idx],
						login_msg.buffer)->payload)); //da rimettere se non si usa un user pointer in usercheck
				aux = client_con;//capirne bene la necessità
				//CONTROLLAREEEEEEEEEE AUX

				/*Si modificano i dati di user e lo si inserisce nella lista utenti online*/
				temp_user->sfd = aux;
				pthread_create(&(temp_user->tid), NULL, worker_start, (void*) temp_user);//fare EC sulla create!!!!!!!!!!!!!!!!!! NON usa perror
				//una volta tolto l'utilizzo di tid posso spostare prima di create la modifica del sfd
//				printf("creato worker %d\n",ttid);
//				temp_user->tid = ttid;
//				temptid = ttid;

				/*Si inserisce il puntatore nella lista*/
				insert_user_l(online_list,temp_user);
				//pthread_mutex_unlock(&users_mtx);

				/// RIMETTERE BLOCCHI LISTAAAAAAAAAA

				break;
			}
				/*utente errato*/
				//cercare di ottimizzare lo switchf

				//nei 2 case sotto mettere solo le newnsg
				//e una send unica dopo
			case -1: {//NO
				//newMessage(MSG_NO, ERR_UNKWN_USER, &reply_msg,strlen(ERR_UNKWN_USER)+1);// salvarsi questo val?
				newMessage(MSG_NO, NULL, &reply_msg, 0);// salvarsi questo val?

				sendMessage(client_con, &reply_msg);
				closeSocket(client_con);//ec??
				break;
			} //si può anche passare null come stringa...

			case -2: { //error
				//newMessage(MSG_ERROR, ERR_ALREADY_ON, &reply_msg,strlen(ERR_ALREADY_ON)+1);
				newMessage(MSG_ERROR, NULL, &reply_msg, 0);
				sendMessage(client_con, &reply_msg);
				closeSocket(client_con);//ec??
				break;
			}

			} //fine switch check
			//pthread_mutex_unlock(&users_mtx);
			//printf("SERVER LIST LOCK SBLOCCATO!!\n");

			//SBLOCCAREEEEEEEEEEEEE


			//fare anche un else, inviando un messaggio di errore al client?
			//LIBERARE I BUFFER INCOMING
		}

		//controllo segnale

		//blocco avvio chiusura e pulizia
		//		printf("SERVER FINE ciclo ascolto errno = %d TID è %d!\n",errno,pthread_self());
		//		printf("SERVER pending_signal = %d!\n", pending_signal);


	}//fine ciclo while

	/*una volta fuori da ciclo di ascolto si aspetta la terminazione dei thread che compongono il server*/

	return;
}

/**
 *
 * o si fa una funzione apposita che cerca l'utente e poi modifica il socket
 * o si fa che il payload diventa direttamente il sfd
 *
 *così è brutto direi..
 *
 * 0 ok -1 non presente -2 già connesso
 * @param usename
 * @return
 */
//da rendere statica?
//fd serve?????
int user_check(char* username) { //se si mantiene user qui modificare commento doxy
	user* u = NULL;
	//come fare per chiamare una sola volta free_user?
	//printf("usercheck %s\n", username);

	//find hash restituisce COPIA del payload
	if ((u = (user*) find_hashElement(users, username)) == NULL) {

		//printf("MSGSERVER findhash non ha trovato l'utente\n");
		return -1;
	}

	/*Trovato un user con il nome richiesto, si verifica se è online*/
	else {
		if (u->sfd != 0) {
			free_user(u);
			return -2;
		}
	/* utente copia da eliminare*/
		free_user(u);
		return 0;
	}

}

/**
 *
 * @return
 */
int signal_handling(void) {
	sigset_t set;
	struct sigaction *sa;
	//perchè non dichiararla invece di allocarla?

	/* Si mascherano tutti i segnali prima di installare gli opportuni gestori */
	EC_NULL_E(sa = calloc(1,sizeof(struct sigaction)),"errore memoria"); //perchè calloc?
	EC_M1_P(sigfillset(&set),"errore sig handler");
	EC_M1_P(pthread_sigmask(SIG_SETMASK,&set,NULL),"errore sig handler");
	memset(&sa, sizeof(sa), 0);
	sa->sa_handler = SIG_IGN;
	EC_M1_P(sigaction(SIGPIPE, sa, NULL), "errore sig handler");
	EC_M1_P(sigaction(SIGQUIT, sa, NULL), "errore sig handler");

	/* Gestione dei segnali */
	sa->sa_handler = sigterm_handler;
	EC_M1_P(sigaction(SIGTERM, sa, NULL),"errore sig handler");
	sa->sa_handler = sigint_handler;
	EC_M1_P(sigaction(SIGINT, sa, NULL),"errore sig handler");

	/* Rimozione della maschera */
	EC_M1_P(sigemptyset(&set), "errore sig handler");
	EC_M1_P(pthread_sigmask(SIG_SETMASK, &set, NULL),"errore sig handler");
	free(sa);
	return 0;
}

/**
 *
 *
 * @param signum
 */
static void sigint_handler(int signum) {
	pending_signal = 1;
}

/**
 *
 * @param signum
 */
static void sigterm_handler(int signum) {
	pending_signal = 1;
}

